package com.frejoys.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.frejoys.common.enums.EAccountType;
import com.frejoys.common.enums.EStatus;
import com.frejoys.common.error.HttpStatus;
import com.frejoys.common.exception.GeneralException;
import com.frejoys.common.service.AliSmsService;
import com.frejoys.common.util.CacheKeyUtil;
import com.frejoys.common.util.RedisUtil;
import com.frejoys.common.util.RequestUtil;
import com.frejoys.common.util.TimeUtil;
import com.frejoys.dao.entity.Verify;
import com.frejoys.dao.mapper.VerifyMapper;
import com.frejoys.service.BlackService;
import com.frejoys.service.VerifyService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Date;

// 验证
@Service
@RequiredArgsConstructor
public class VerifyServiceImpl extends ServiceImpl<VerifyMapper, Verify> implements VerifyService {

    private final RedisUtil redisUtil;

    private final BlackService blackService;

    private final AliSmsService aliSmsService;

    private Integer timeout = 300; // 短信有效秒数

    @Override
    public Integer getValidSmsId(String accountExtend, String account, Integer type, String code) {
        Integer fNt = TimeUtil.unixTime() - timeout;
        LambdaQueryWrapper<Verify> wrapper = new QueryWrapper().lambda();
        wrapper.select(Verify::getId).eq(Verify::getAccount, account).eq(Verify::getType, type).gt(Verify::getCreateTime, fNt) .eq(Verify::getStatus, EStatus.off.getValue());

        if (!StrUtil.isEmpty(accountExtend)) {
            wrapper.eq(Verify::getAccountExtend, accountExtend);
        }

        if (!StrUtil.isEmpty(code)) {
            wrapper.eq(Verify::getCode, code);
        }

        wrapper.orderByDesc(Verify::getId);
        Verify s = getOnly(wrapper);
        return s != null ? s.getId() : null;
    }

    @Override
    public boolean sendVerifyCode(String accountExtend, String account, Integer type) {
        String lockKey = CacheKeyUtil.accountCodeLockCacheKey(account, type);
        if (redisUtil.setNxEx(lockKey, 1, 5L)) {

            // 获取验证码是否存在
            String accountExpireKey = CacheKeyUtil.accountCodeExpireCacheKey(account, type);
            long et = redisUtil.getExpire(accountExpireKey);
            if (et > 0) {
                redisUtil.del(lockKey);
                throw new GeneralException(HttpStatus.codeAlreadySend);
            }

            Date date = DateUtil.date();
            String ymd = DateUtil.format(date, "yyyyMMdd");

            // 2小时内10条
            String accountHourCountKey = CacheKeyUtil.accountCodeHourCountCacheKey(account, type, ymd);
            long accountHourCount = redisUtil.incr(accountHourCountKey);
            if (accountHourCount == 1) {
                redisUtil.expire(accountHourCountKey, TimeUtil.hourSecond * 2);
            } else if (accountHourCount > 10) {
                redisUtil.del(lockKey);
                throw new GeneralException(HttpStatus.codeOutLimit);
            }


            // 当日验证码最大数量
            String accountDayCountKey = CacheKeyUtil.accountCodeDayCountCacheKey(account, type, ymd);
            long accountDayCount = redisUtil.incr(accountDayCountKey);
            if (accountDayCount == 1) {
                redisUtil.expire(accountDayCountKey, TimeUtil.dayExpireSecond());
            } else if (accountDayCount > 20) {
                redisUtil.del(lockKey);
                throw new GeneralException(HttpStatus.codeOutLimit);
            }

            // 当日ip验证码最大数量
            String ipCountKey = CacheKeyUtil.ipCodeCountCacheKey(RequestUtil.ip(), ymd);
            long ipCount = redisUtil.incr(ipCountKey);
            if (ipCount == 1) {
                redisUtil.expire(ipCountKey, TimeUtil.dayExpireSecond());
            } else if (ipCount > 20) {
                redisUtil.del(lockKey);
                throw new GeneralException(HttpStatus.codeOutLimit);
            }

            // 效验黑名单账户
            blackService.verifyAccount(accountExtend, account, type);
            // 效验ip黑名单
            blackService.verifyIp(RequestUtil.ip());

            // 是否存在有效的验证码
            Integer sid = getValidSmsId(accountExtend, account, type, null);
            if (sid != null) {
                redisUtil.del(lockKey);
                throw new GeneralException(HttpStatus.codeAlreadySend);
            }

            // 生成6位验证码
            Integer code = RandomUtil.randomInt(100000, 999999);

            // 发送验证码
            if (type.equals(EAccountType.phone.getValue())) {
                 aliSmsService.sendSms(account, code.toString());
            }

            // 保存发送信息
            Verify v = new Verify(){{
                setAccountExtend(accountExtend);
                setAccount(account);
                setType(type);
                setCode(code.toString());
                setIpAddr(NetUtil.ipv4ToLong(RequestUtil.ip()));
                setCreateTime(TimeUtil.unixTime());
            }};
            save(v);

            // 验证码计时
            redisUtil.set(accountExpireKey, code, timeout.longValue());
            // 删除锁
            redisUtil.del(lockKey);

            return true;
        } else {
            throw new GeneralException(HttpStatus.beingProcessed);
        }
    }

    @Override
    public boolean verifyCode(String accountExtend, String account, Integer type, String code) {
        // 获取是否存在此信息
        Integer sid = getValidSmsId(accountExtend, account, type, code);

        if (sid == null) {
            throw new GeneralException(HttpStatus.codeVerifyError);
        }

        LambdaUpdateWrapper<Verify> wrapper = new UpdateWrapper().lambda();
        wrapper.set(Verify::getStatus, EStatus.on.getValue());
        wrapper.eq(Verify::getId, sid);
        return update(wrapper);
    }

}